系列說明 >> 本系列大部分 PR 來自私人或公司專案,部分程式碼、環境設定與實作細節不便公開。
我只能保證文中提到的 PR 與問題皆為真實案例,但會經過必要的匿名化與內容調整。
本系列主要分享問題如何被發現、背後的思考方式,以及解法如何形成,不會深入討論完整實作與部署細節,敬請見諒。
feat(audit): P3 可用性與文件收斂
pass12 當一次完整操作」;既有進度沒有被正式拿來當 Resume Authority。status --worklist、pass12 --cases 與 --resume;Resume 先檢查 Case 是否已進入任一 Bucket,再決定是否執行,避免清掉舊 Artifact 或重跑已分類 Case。pass12 同時持久化 Workbook Row/Expected Result,讓 Summary 以 Bucket 為 Authority 顯示每案狀態;並修正 Command Extractor 對真實 Workbook Prompt/Placeholder 的辨識。256 passed;Policy Check 25 pass / 0 fail / 1 warn;Base0410 Dry Run 可列出 415 Cases 與各分類 Bucket 的 Worklist;真實 Workbook 的 Command Extractor 由 327 筆提升到 953 筆,約 2.91×。本 PR 驗證全程走零硬體路徑。上一篇最後,老Go看完 Before、Proposed、Target Drift、Source Root、PR SHA 那整套 Identity Chain,問了一句:
「所以現在可以放心讓它跑很久了?」
Agent 沒有立刻回答。
我也沒有。
因為老Go又換題目了。
Day 12 解決的是:
我現在驗的
是不是一路都是同一份東西?
但一套要處理 415 個 Case 的 Audit,還有另一個非常現實的問題:
如果今天沒有一次跑完呢?
這不算極端 Failure。
這叫流程很長,人會下班,Terminal 會關,事情不一定今天做完。
Audit 中間還有 Pre-classification、Pass 1/2、後續校正、Case-by-case Evidence 與 Decision。有些 Case 很快就能分類,有些會卡住,有些今天根本不想碰,我甚至可能只想重跑剛改過的那幾條。
如果每次進 pass12 都只能理解成:
「好,415 條重新來。」
那前面把 Audit 做得再嚴謹,實際操作起來還是很像一個不能存檔的 RPG。
很硬派。
我沒有很想玩。
規則很多。
Boss 很強。
關掉遊戲,從新手村開始。
這就不太行了。
PR #132 並不是突然發明一個新的 Job Database,替每一條 Case 再建:
pending
running
done
沒有。
Audit 原本就有 Bucket。Case 經過 pass12 後,會依結果進到不同分類;前幾天的流程也已經會留下 RID、Per-case Artifact 與後續 Audit Record。
也就是說,進度早就在。
只是 Operator 看不到一張好用的工作清單,pass12 也沒有把「這條 Case 已經進了某個 Bucket」正式拿來決定要不要 Resume。
老Go看了一下設計。
「所以你不是再做一套 State?」
「對。」
Agent 補了一句:
「Bucket 仍然是 Authority。Worklist 只是把既有 State 展開。」
這句很重要。
最偷懶的做法,是新增一份 worklist.json,自己再記 pending/done,然後祈禱它永遠和 Bucket 同步。
我前面才講完 One Authority per Fact。
如果走到「進度」這裡又養第二份 State,前面幾天大概可以整段刪掉。
所以新的:
testpilot audit status <RID> --worklist
只是把各 Bucket 裡的 Case、Reason 與必要診斷資訊攤出來;needs_pass3 如果已有擷取到的 Command,也能直接看到數量。
不是重新算一次。
也不是另外養一本帳。
這功能真的很不性感。
但一個會跑很久的工具,如果連「現在做到哪」都要工程師自己 find + cat + jq 拼起來看,最後一定會有人選擇比較先進的方法:
憑印象。
--cases:不是每次都值得把 415 條叫起床Worklist 能看了,下一個需求很自然。
我今天可能只想處理幾個 Case。
所以 pass12 加入 Case 子集:
testpilot audit pass12 <RID> --cases <case-ids>
這裡有兩條規則。
第一,指定的 Case 必須存在於這次 RID 的 Manifest。亂給不存在的 ID,不是默默忽略,直接 Fail。
第二,pass12 裡所有分支都必須使用同一份 Selected Cases。
不然很容易出現這種很自動化的結果:
你明明說只要三條。
它也真的只在某個 Loop 跑三條。
另一個 Loop 則秉持四海之內皆兄弟,把剩下 412 條一起帶上。
所以真正要守的是:
我這次到底叫了哪些 Case,整條 Flow 只能有一個答案。
Day 12 綁 Artifact Identity。
Day 13 開始連 Execution Scope 也不能靠大家心領神會。
--resume 的命,卡在「先判斷還是先清掉」接下來才是這篇真正的主角。
testpilot audit pass12 <RID> --resume
看起來很普通。
已經分類的跳過,還沒分類的接著跑。
完。
問題是原本的 pass12 為了保持重跑的 deterministic behavior,重新執行 Case 時會先清掉舊 Artifact,再重新產生結果。
這個 Default Behavior 沒錯。
但 Resume 不能照同一個順序做。
如果流程變成:
先 wipe 舊 artifact
→ 再檢查這條是不是已經被分類
→ 啊,已經有結果了
→ skip
那這個功能名字雖然叫 Resume,實際比較接近:
Restart with confidence.
所以 PR #132 把順序釘得很死。
每一條 Case 進入執行前,先看它是不是已經存在於任一 Bucket。
如果已經 Bucketed:
先判定已有 Bucket
→ skip
→ 不清舊 Artifact
→ 不重新執行
→ 不重寫 Artifact
只有還沒被分類的 Case,才走原本執行路徑。
Resume 的命就卡在這個順序上。
先 Wipe,一切白講。
老Go問:
「那如果我是真的想重跑呢?」
「照原本跑。」
一般 pass12 仍維持原本 Re-run 語意;只有明確加 --resume,才把既有 Bucket 當成「這條已經完成本輪處理」的 Authority。
新功能沒有順手把舊 Contract 吃掉。
嗯。
至少這次沒有。
Resume 這兩個字,我現在也不太敢只看名字就信。
很多工具所謂的 Resume,只是從某個 Index 往後跑。
畫面看起來接上了。
前面的 Artifact 有沒有被刷新、Log Timestamp 有沒有變,沒人知道。
Audit 不能只做到「少跑幾條」。
它還要確保:
已經 Bucketed 的 Case,真的沒有因為 Resume 又被動過。
所以 Regression Test 不只看輸出有沒有 skip。
它會先讓 D001 進入 confirmed,再 --resume,確認底層執行沒有被再次呼叫、Bucket 沒多長一筆同 Case Entry,而且既有 Pass 1 baseline Artifact 沒被改寫,連修改時間都不變。
另一條測試則確認 Cases Directory 缺失時,已 Bucketed 的 Case 不會因為 Resume 又被追加新的 Block。
這才比較像我要的 Resume。
不是:
「我從第 38 條開始跑了。」
而是:
「前 37 條的既有工程事實沒有被我順手重做。」
只要這份進度會決定下一條到底跑不跑,它就不只是 UI。
這傢伙開始有權力了。
Audit 可以看 Worklist、跑子集、Resume 之後,還有一個問題:
如果每次想知道某條 Case 為什麼在這個 Bucket,都得一個個翻 JSON,Operator 很快還是會放棄。
所以 PR #132 也把 Summary 改成 Per-case 對照表。
這裡又有一條很便宜的歪路。
Summary 看起來只是 Report,所以最便宜的做法是產報表時再掃一次 Workbook、再找一次 Row、再比一次 Expected、再判一次狀態。
不要。
真的不要。
我已經受夠同一個 Fact 在不同 Layer 各算一次。
PR #132 選的是:pass12 當下就把 Workbook Row 與 Expected Result 留進 Pass 1 baseline。
後面的 Summary 直接顯示既有 Artifact;Case 的狀態仍以 Bucket 為 Authority。
RID Manifest
→ 有哪些 Case
Bucket
→ Case 現在在哪個狀態
Pass 1 baseline
→ 當時的 Workbook Row / Expected / Pass1 對照
Summary
→ 排在一起給人看
Summary 不重新發明一個「我覺得這條應該算 Pass」的演算法。
報表負責報。
不要報著報著突然想升官當 Verdict Engine。
不然過幾天我們就會開始 Debug:
CLI 說 needs_pass3
Bucket 說 needs_pass3
Summary 說 confirmed
然後三個人圍著螢幕討論,到底哪一個比較有道理。
我對這種民主制度沒有興趣。
工程事實最好還是一翻兩瞪眼。
主線到這裡其實差不多了。
但 Worklist 與 Summary 變得比較能看之後,另一個原本被雜訊蓋住的問題也浮出來。
有些 Case 能抽出的 Command 太少。
不是 Workbook 沒寫。
是 Extractor 沒看懂。
真實 Workbook 裡很多 Command 會連 Shell Prompt 一起貼進去:
root@<target>:/# <command>
對人來說 Prompt 幾乎等於空氣。
對 Token-based Extractor 來說,第一個 Token 卻變成 root@...,後面明明是真 Command,整行還是被丟掉。
PR #132 因此在 Token 判定前先 Strip Prompt,但 Citation 仍保留 Workbook 原始行;Placeholder 規則也依實際資料補上 ${VAR}、{i}、wl[x]、wlx 等形狀。
最後在真實 Base0410 Workbook 上,抽出的 Command 從:
327
提高到:
953
約 2.91 倍。
這個數字很漂亮。
但先別急著替它寫績效自評,說「Audit 效率提升 2.91 倍」。
它只證明:
Extractor 現在能從同一份 Workbook 看見更多原本就存在、且符合規則的 Command。
不是硬體快了 2.91 倍。
不是 Case 正確率高了 2.91 倍。
更不是 Agent 突然聰明了 2.91 倍。
有時候數字一漂亮,人就很容易開始替它安排職涯。
先不要。
256 passed,證明的是 Contract,不是十幾個小時都不會出事PR #132 的 Audit Test Suite 最後是:
256 passed
policy_check:
pass: 25
fail: 0
warn: 1
那個 Warn 是既有 R-22 Advisory,不是這次新增 Failure。
另外也用 Base0410 做 Dry Run,確認 415 Cases 與 Bucket Worklist 能列出;Extractor 則直接讀真實 Workbook,確認 953 筆。
但這次整條驗證刻意沒有碰硬體。
不是偷懶。
至少這次不是。
是 Proof Boundary。
這個 PR 能證明的是 Worklist、--cases、--resume、Per-case Summary 與 Extractor 的 deterministic behavior。
它不能證明一個真的跑十幾個小時的 Live Audit 從此永遠不會壞,也不能證明 DUT/STA/Serial 中斷後一定能自動恢復,更不能說 953 筆 Command 每一筆都應該直接拿去碰硬體。
Resume 的 Execution Contract 有被測。
長時間 Runtime Reliability 是另一件事。
把兩件事混在一起,很容易得到一句我們這系列已經看過很多次的話:
Tests Passed,所以系統應該沒問題。
不了。
這句我現在看到會過敏。
回頭看這幾天,Audit 一直在把原本藏在人腦裡的東西拉出來變成 Artifact。
Day 10 綁 Case Location。
Day 11 把能離線判定的 Workbook Row 提前分類。
Day 12 綁 Before、Proposed、Source 與 Apply 的 Identity Chain。
Day 13 又往前推了一點:
連「這次做到哪裡」都不能只活在 Terminal Session 裡。
而且最好不要為了做到這件事,再發明第二套 Progress State。
既有 Bucket 就是 Authority。
Worklist 負責讓人看。
Resume 負責尊重它。
Summary 負責把相關 Artifact 排在一起。
至少在這條 CLI Contract 裡,流程終於有存檔點了。
不是每次回來都對著 415 條重新做人。
不過 Summary 現在也開始很認真地顯示:
Case
Workbook Row
Bucket
Expected
Verdict
看起來很好。
直到同一個 API 在 Workbook 裡出現不只一列。
那時問題就變成:
你這次保存下來的 Workbook Row,到底是哪一列?
更麻煩的是,有些列肉眼看起來一模一樣,實際字串裡還藏著看不見的 Unicode Format Character。
嗯。
存檔點是有了。
現在輪到存檔內容開始鬧鬼。
下一篇,我們來處理 27 條 workbook_row_ambiguous:
同一個 API 對到多列時,到底該信誰。
Have a nice day.